<?php

declare(strict_types=1);
/**
 * This file is part of MoChat.
 * @link     https://mo.chat
 * @document https://mochat.wiki
 * @contact  group@mo.chat
 * @license  https://github.com/mochat-cloud/mochat/blob/master/LICENSE
 */
namespace MoChat\Plugin\AdminTenant\Action\Admin;

use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\DbConnection\Db;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\Middleware;
use Hyperf\HttpServer\Annotation\Middlewares;
use Hyperf\HttpServer\Annotation\RequestMapping;
use Hyperf\Utils\Codec\Json;
use MoChat\App\Tenant\Contract\TenantMenuPackageContract;
use MoChat\Framework\Action\AbstractAction;
use MoChat\Framework\Constants\ErrorCode;
use MoChat\Framework\Exception\CommonException;
use MoChat\Framework\Request\ValidateSceneTrait;
use MoChat\Plugin\AdminTenant\Action\Admin\Traits\OrderTrait;
use MoChat\Plugin\AdminUser\Contract\AdminUserContract;
use MoChat\Plugin\AdminUser\Middleware\AdminAuthMiddleware;
use MoChat\Plugin\AdminUser\Middleware\PermissionMiddleware;
use MoChat\Plugin\AdminWallet\Contract\AdminWalletContract;
use MoChat\Plugin\TenantOrder\Contract\TenantOrderContract;
use MoChat\Plugin\TenantProduct\Contract\TenantProductSkuContract;

/**
 * 租户- 续费.
 *
 * Class Recharge.
 * @Controller
 */
class Recharge extends AbstractAction
{
    use ValidateSceneTrait;
    use OrderTrait;

    /**
     * @Inject
     * @var AdminUserContract
     */
    protected $adminUserService;

    /**
     * @Inject
     * @var AdminWalletContract
     */
    protected $adminWalletService;

    /**
     * @Inject
     * @var TenantProductSkuContract
     */
    protected $tenantProductSkuService;

    /**
     * @Inject
     * @var TenantOrderContract
     */
    protected $tenantOrderService;

    /**
     * @Inject
     * @var TenantMenuPackageContract
     */
    protected $tenantMenuPackageService;

    /**
     * @Inject
     * @var StdoutLoggerInterface
     */
    private $logger;

    /**
     * @RequestMapping(path="/admin/tenant/recharge", methods="post")
     * @Middlewares({
     *     @Middleware(AdminAuthMiddleware::class),
     *     @Middleware(PermissionMiddleware::class)
     * })
     * @return array 返回数组
     */
    public function handle(): array
    {
        $adminUser = user();
        // 参数验证
        $this->validated($this->request->all());
        // 接收参数
        $params = [
            'type' => (int) $this->request->input('type'), // 1续费，2升级
            'tenantId' => (int) $this->request->input('tenantId'), // 租户id
            'skuId' => (int) $this->request->input('skuId'), // 套餐id
            'maxEmployee' => (int) $this->request->input('maxEmployee'),
            'maxCorp' => (int) $this->request->input('maxCorp'),
        ];
        // 租户
        $tenant = $this->tenantService->getTenantById($params['tenantId'], ['id', 'max_employee', 'max_corp', 'expired_at', 'sku_id', 'owner_id']);

        // 余额检查、订单信息处理
        $order = $this->handleParamsOrder($params, $adminUser, $tenant);

        // 数据操作
        Db::beginTransaction();
        try {
            // 订单&代理金币处理
            $this->order($params['skuId'], $order, $params['tenantId'], $adminUser);
            // 升级套餐修改租户信息
            $maxEmployee = $params['maxEmployee'] > $tenant['maxEmployee'] ? $params['maxEmployee'] : $tenant['maxEmployee'];
            $maxCorp = $params['maxCorp'] > $tenant['maxCorp'] ? $params['maxCorp'] : $tenant['maxCorp'];
            if ($params['type'] === 2) {
                $menuPackageId = $this->tenantMenuPackageService->getTenantMenuPackageBySkuId($params['skuId'], ['id']);
                $tenantData = [
                    'sku_id' => $params['skuId'],
                    'role_menu' => $order['roleMenu'],
                    'max_employee' => $maxEmployee,
                    'max_corp' => $maxCorp,
                    'menu_package_id' => $menuPackageId['id'] ?? 0,
                ];
                $this->tenantService->updateTenantById($params['tenantId'], $tenantData);
            }
            // 处理租户过期时间
            $this->handleExpiredAt($tenant, $order, $params['type']);
            Db::commit();
        } catch (\Throwable $e) {
            Db::rollBack();
            $this->logger->error(sprintf('%s [%s] %s', '代理充值失败', date('Y-m-d H:i:s'), $e->getMessage()));
            $this->logger->error($e->getTraceAsString());
            throw new CommonException(ErrorCode::SERVER_ERROR, '代理充值失败');
        }
        return [];
    }

    protected function handleParamsOrder(array $params, array $adminUser, array $tenant): array
    {
        // 数据处理
        $order = ['walletBalance' => 0, 'itemTotal' => 0, 'adjustmentTotal' => 0, 'total' => 0, 'stock' => 0, 'cycle' => 1, 'roleMenu' => '', 'userId' => 0];

        // 代理金币余额
        $user = $this->adminUserService->getAdminUserById($adminUser['id'], ['level', 'wallet_balance']);
        $order['userId'] = $adminUser['id']; // 代理id
        if ($adminUser['isRoot'] === 1) {// 管理员续费
            $user = $this->adminUserService->getAdminUserByStatus($tenant['ownerId'], 1);
            if (empty($user)) {
                throw new CommonException(ErrorCode::INVALID_PARAMS, '上级代理已禁用或删除，续费失败');
            }
            $order['userId'] = $tenant['ownerId'];
        }
        $order['walletBalance'] = $user['walletBalance'];

        // 套餐
        $sku = $this->tenantProductSkuService->getTenantProductSkuById($params['skuId'], ['attributes', 'price', 'stock']);
        if ($sku['stock'] <= 0) {
            throw new CommonException(ErrorCode::INVALID_PARAMS, '库存数量不足，创建失败');
        }
        // 升级套餐 价格必须高于租户原套餐
        $oldSku = $this->tenantProductSkuService->getTenantProductSkuByIdWithTrashed($tenant['skuId'], ['attributes', 'price']);
        if ($params['type'] === 2) {
            if ($oldSku['price'] >= $sku['price']) {
                throw new CommonException(ErrorCode::INVALID_PARAMS, '升级套餐不允许降低');
            }
        }

        $attributes = $this->tenantProductSkuService->getTenantProductSkuValuesById(explode(',', $sku['attributes']));
        foreach ($attributes as $v) {
            if ($v['attributeName'] === '服务周期') {
                $order['cycle'] = $v['attributeValue'];
            }
            if ($v['attributeName'] === '套餐功能') {
                $order['roleMenu'] = $v['attributeValue'];
            }
        }
        $order['stock'] = $sku['stock'];
        $order['itemTotal'] = $sku['price'] * 100;

        $adminUser = user();
        if ($adminUser['isRoot'] === 1) {
            $priceRule = [
                'type' => 1,
                'discount' => 10,
            ];
        } else {
            // 代理扣币规则
            $level = $this->adminUserLevelService->getAdminUserLevelById($user['level'], ['price_rule']);
            $priceRule = Json::decode($level['priceRule']);
        }

        // 统一折扣
        if ((int) $priceRule['type'] === 1) {
            $price = number_format($sku['price'] * $priceRule['discount'] / 10, 2, '.', '');
            $order['adjustmentTotal'] = ($sku['price'] - $price) * 100;
            $order['total'] = $price * 100;
            if ($user['walletBalance'] < $price) {
                throw new CommonException(ErrorCode::INVALID_PARAMS, '金币余额不足，请充值');
            }
        }

        // 按套餐扣币
        if ((int) $priceRule['type'] === 2) {
            if (in_array($params['skuId'], array_column($priceRule['sku'], 'id'), true)) {
                foreach ($priceRule['sku'] as $item) {
                    if ($params['skuId'] == $item['id']) {
                        $price = $item['price'];
                        $order['adjustmentTotal'] = ($sku['price'] - $price) * 100;
                        $order['total'] = $price * 100;
                        if ($user['walletBalance'] < $item['price']) {
                            throw new CommonException(ErrorCode::INVALID_PARAMS, '金币余额不足，请充值');
                        }
                    }
                }
            } else {
                $price = $sku['price'];
                $order['total'] = $price * 100;
                if ($user['walletBalance'] < $sku['price']) {
                    throw new CommonException(ErrorCode::INVALID_PARAMS, '金币余额不足，请充值');
                }
            }
        }

        // 升级套餐扣币,原套餐未过期
        $today = date('Y-m-d');
        if ($params['type'] === 2 && $tenant['expiredAt'] > $today) {
            // 1、原套餐每日金额 = 用原套餐的金额 / 服务时长
            $orderInfo = $this->tenantOrderService->getTenantOrderByTenantIdSkuId($params['tenantId'], $tenant['skuId']);
            $oldAttributes = $this->tenantProductSkuService->getTenantProductSkuValuesById(explode(',', $oldSku['attributes']));
            foreach ($oldAttributes as $v) {
                if ($v['attributeName'] === '服务周期') {
                    $oldSku['cycle'] = $v['attributeValue'];
                    break;
                }
            }
            $oldUnitPrice = number_format($orderInfo['total'] / $oldSku['cycle'], 2, '.', '');
            // 3、剩余的金额 = 原套餐每日金额 * 剩余时长
            $balanceMoney = $oldUnitPrice * ((strtotime($tenant['expiredAt']) - strtotime($today)) / 86400);
            // 4、扣币规则 = 新套餐金额 - 剩余的金额
            $order['total'] -= $balanceMoney;
            if ($order['total'] < 0) {
                throw new CommonException(ErrorCode::INVALID_PARAMS, '套餐升级失败，请重新选择套餐');
            }
            $order['adjustmentTotal'] += $balanceMoney;
        }

        return $order;
    }

    /**
     * 到期时间.
     */
    protected function handleExpiredAt(array $tenant, array $order, int $type)
    {
        $today = date('Y-m-d');
        // 原套餐已过期
        $expiredAt = date('Y-m-d', strtotime("+{$order['cycle']} day"));

        // 原套餐未过期
        if ($tenant['expiredAt'] > $today) {
            $expiredAt = date('Y-m-d', strtotime($tenant['expiredAt'] . "+{$order['cycle']}day"));
            if ($type === 2) {
                // 新的到期时间 新套餐时间-老套餐已使用时间=剩余时间，用剩余时间+当前时间=新的到期时间
                $oldSku = $this->tenantProductSkuService->getTenantProductSkuByIdWithTrashed($tenant['skuId'], ['attributes']);
                $attributes = $this->tenantProductSkuService->getTenantProductSkuValuesById(explode(',', $oldSku['attributes']));
                // 老套餐时间
                $oldCycle = 0;
                foreach ($attributes as $v) {
                    if ($v['attributeName'] === '服务周期') {
                        $oldCycle = $v['attributeValue'];
                        break;
                    }
                }
                // 老套餐已使用时间 = 老套餐时间-老套餐剩余时间
                $oldUseDays = $oldCycle - (strtotime($tenant['expiredAt']) - strtotime($today)) / 86400;
                // 剩余时间 = 新套餐时间-老套餐已使用时间
                $balanceDays = $order['cycle'] - $oldUseDays;
                if ($balanceDays <= 0) {
                    throw new CommonException(ErrorCode::INVALID_PARAMS, '套餐服务周期不足，请重新选择');
                }
                $expiredAt = date('Y-m-d', strtotime("+{$balanceDays} day"));
            }
        }

        $this->tenantService->updateTenantById($tenant['id'], ['expired_at' => $expiredAt]);
    }

    /**
     * 验证规则.
     *
     * @return array 响应数据
     */
    protected function rules(): array
    {
        return [
            'tenantId' => 'required | integer | min:1 | bail',
            'type' => 'required | integer | in:1,2, | bail',
            'skuId' => 'required | integer | min:1 | bail',
            'maxEmployee' => 'required | integer | min:1 | bail',
            'maxCorp' => 'required | integer | min:1 | bail',
        ];
    }

    /**
     * 验证错误提示.
     * @return array 响应数据
     */
    protected function messages(): array
    {
        return [
            'tenantId.required' => '代理id 必填',
            'tenantId.integer' => '代理 必需为整数',
            'tenantId.min' => '代理 值不可小于1',
            'type.required' => '类型 必填',
            'type.integer' => '类型 必需为整数',
            'type.in' => '类型 值必须在列表内：[1,2]',
            'money.required' => '充值金额 必填',
            'money.string' => '充值金额 必需为字符串',
            'skuId.required' => '套餐id 必填',
            'skuId.integer' => '套餐id 必需为整数',
            'skuId.min' => '套餐id 值不可小于1',
            'maxEmployee.required' => '限制员工数 必填',
            'maxEmployee.integer' => '限制员工数 必需为整数',
            'maxCorp.required' => '限制账号数 必填',
            'maxCorp.integer' => '限制账号数 必需为整数',
        ];
    }
}
